/*
 *  Copyright (C) 2010 Pete Reisinger <p.reisinger@gmail.com>.
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package paypalnvp.request;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import paypalnvp.fields.ShipToAddress;
import paypalnvp.fields.Address;
import paypalnvp.fields.BillingAgreement;
import paypalnvp.fields.BuyerDetails;
import paypalnvp.fields.PaymentAction;
import paypalnvp.fields.Payment;
import paypalnvp.fields.ShippingOptions;
import paypalnvp.util.Validator;

/**
 * Instance is used for SetExpressCheckout request. This request initiates an
 * Express Checkout transaction.
 *
 * @author Pete Reisinger <p.reisinger@gmail.com>.
 */
public final class SetExpressCheckout implements Request {

    /** Method value of this request */
    private static final String METHOD_NAME = "SetExpressCheckout";

    /** Locale of pages displayed by PayPal during Express Checkout. */
    public enum LocalCode {

        /** Australia */
        AU,

        /** Austria */
        AT,

        /** Belgium */
        BE,

        /** Canada */
        CA,

        /** Switzerland */
        CH,

        /** China */
        CN,

        /** Germany */
        DE,

        /** Spain */
        ES,

        /** United Kingdom */
        GB,

        /** France */
        FR,

        /** Italy */
        IT,

        /** Netherlands */
        NL,

        /** Poland */
        PL,

        /** United States */
        US;
    }

    /** Type of checkout flow */
    public enum SolutionType {
    
        /** Express Checkout for auctions */
        SOLE,

        /** Normal Express Checkout */
        MARK
    }

    /** Type of PayPal page to display */
    public enum LandingPage {

        /** non-PayPal account */
        BILLING,

        /** PayPal account login */
        LOGIN
    }

    /** Type of channel */
    public enum ChannelType {

        /** non-auction seller */
        MERCHANT,

        /** eBay auction */
        EBAYITEM
    }

    /** name value pair request */
    private final Map<String, String> nvpRequest;

    /** name value pair response */
    private Map<String, String> nvpResponse;

    /** shipping options, empty if no options set */
    private List<Map<String, String>> shippingOptions;

    /** billing agreement (recurring payment etc.), empty if no agreemnt set */
    private List<Map<String, String>> billingAgreement;

    /**
     * PayPal recommends that the returnUrl be the final review page on which
     * the customer confirms the order and payment or billing agreement.
     *
     * PayPal recommends that the cancelUrl be the original page on which the
     * customer chose to pay with PayPal or establish a billing agreement.
     * 
     * @param payment
     * @param returnUrl URL to which the customer’s browser is returned after
     *                  choosing to pay with PayPal. Maximum 2048 characters.
     * @param cancelUrl URL to which the customer is returned if he does not
     *                  approve the use of PayPal to pay you. Maximum 2048
     *                  characters.
     * @throws IllegalArgumentException
     */
    public SetExpressCheckout(Payment payment, String returnUrl,
            String cancelUrl) throws IllegalArgumentException {

        /* cancel url and return url has to be less or equal to 2048 chars */
        if (returnUrl.length() >= 2048) {
            throw new IllegalArgumentException("returnUrl cannot be longer "
                    + "than 2048 characters.");
        }
        if (cancelUrl.length() >= 2048) {
            throw new IllegalArgumentException("cancelUrl cannot be longer "
                    + "than 2048 characters.");
        }
        if (payment == null || returnUrl == null || cancelUrl == null) {
            throw new IllegalArgumentException("Arguments cannot be null");
        }

        nvpResponse = new HashMap<String, String>();
        nvpRequest  = new HashMap<String, String>();
        shippingOptions = new LinkedList<Map<String, String>>();
        billingAgreement = new LinkedList<Map<String, String>>();

        nvpRequest.put("METHOD", METHOD_NAME);

        /* copy nvp from payment */
		HashMap<String, String> nvp =
				new HashMap<String, String>(payment.getNVPRequest());
        nvpRequest.putAll(nvp);

        nvpRequest.put("RETURNURL", returnUrl);
        nvpRequest.put("CANCELURL", cancelUrl);
    }

    /**
     *
     * @param token A timestamp token
     * @throws IllegalArgumentException
     */
    public void setToken(String token) throws IllegalArgumentException {

        if (token.length() != 20) {
            throw new IllegalArgumentException("Invalid token argument");
        }
        nvpRequest.put("TOKEN", token);
    }

    /**
     * The expected maximum total amount of the complete order, including
     * shipping cost and tax charges.
     * If the transaction does not include a one-time purchase, this field is
     * ignored.
     * Limitations: Must not exceed $10,000 USD in any currency. No currency
     * symbol. Must have two decimal places, decimal separator must be a
     * period (.), and no thousands separator.
     *
     * @param maxAmount number with exactly two decimal places
     * @throws IllegalArgumentException
     */
    public void setMaxAmount(String maxAmount) throws IllegalArgumentException {

        /* amount is number with exactly two decimal places */
        if (!Validator.isValidAmount(maxAmount)) {
            throw new IllegalArgumentException("Amount " + maxAmount +
                    " is not valid. Amount has to have exactly two decimal "
                    + "places seaprated by \".\" - example: \"50.00\"");
        }

        /* values for this request */
        nvpRequest.put("MAXAMT", maxAmount);
    }

    /**
     * URL to which the callback request from PayPal is sent. It must start
     * with HTTPS for production integration. It can start with HTTPS or HTTP
     * for sandbox testing.
     *
     * @param callback max 1024 characters
     */
    public void setCallback(String callback) throws IllegalArgumentException {

        if (callback.length() > 1024) {
            throw new IllegalArgumentException("Callback can be maximum 1024 "
                    + "in length");
        }
        nvpRequest.put("CALLBACK", callback);
    }

    /**
     * An override for you to request more or less time to be able to process
     * the callback request and respond. The acceptable range for the override
     * is 1 to 6 seconds.
     *
     * @param timeout integer has to be between 1 - 6
     */
    public void setCallbackTimeout(int timeout) {

        if (timeout < 1 || timeout > 6) {
            throw new IllegalArgumentException("Timeout has to be between 1 - 6");
        }

        nvpRequest.put("CALLBACKTIMEOUT", Integer.toString(timeout));
    }

    /**
     * Indicates that you require that the customer’s shipping address on file
     * with PayPal be a confirmed address.
     * Setting this field overrides the setting you have specified in your
     * Merchant Account Profile.
     *
     * @param required if true than confirmed address is required
     */
    public void setRequireConfirmedShipping(boolean required) {

        String req = (required) ? "1" : "0";
        nvpRequest.put("REQCONFIRMSHIPPING", req);
    }

    /**
     * Indicates that on the PayPal pages, no shipping address fields should be 
     * displayed whatsoever.
     * 
     * @param noShipping if true, no address fields will be displayed
     */
    public void setNoShipping(boolean noShipping) {

        String no = (noShipping) ? "1" : "0";
        nvpRequest.put("NOSHIPPING", no);
    }

    /**
     * Indicates that the customer may enter a note to the merchant on the 
     * PayPal page during checkout. The note is returned in the 
     * GetExpressCheckoutDetails response and the DoExpressCheckoutPayment 
     * response.
     * 
     * @param allowNote if true, note can be entered by customer
     */
    public void setAllowNote(boolean allowNote) {
        
        String note = (allowNote) ? "1" : "0";
        nvpRequest.put("ALLOWNOTE", note);
    }

    /**
     * Indicates that the PayPal pages should display the shipping address set 
     * by you in this SetExpressCheckout request, not the shipping address on 
     * file with PayPal for this customer.
     * Displaying the PayPal street address on file does not allow the customer 
     * to edit that address.
     * Set address using setAddress(ShipToAddress address) method
     * 
     * @param rOverride if true set address will be used
     */
    public void setAddressOverride(boolean rOverride) {

        String over = (rOverride) ? "1" : "0";
        nvpRequest.put("ADDROVERRIDE", over);
    }

    /**
     * Locale of pages displayed by PayPal during Express Checkout.
     * 
     * @param localCode
     */
    public void setLocalCode(LocalCode localCode) {
        nvpRequest.put("LOCALECODE", localCode.toString());
    }

    /**
     * Sets the Custom Payment Page Style for payment pages associated with 
     * this button/link. This value corresponds to the HTML variable page_style 
     * for customizing payment pages. The value is the same as the Page Style 
     * Name you chose when adding or editing the page style from the Profile 
     * subtab of the My Account tab of your PayPal account.
     * 
     * Character length and limitations: 30 single-byte alphabetic characters
     * 
     * @param pageStyle 
     * @throws IllegalArgumentException
     */
    public void setPageStyle(String pageStyle) throws IllegalArgumentException {

        if (pageStyle.length() > 30) {
            throw new IllegalArgumentException("Character length exceeded 30 " 
                    + "characters");
        }
        nvpRequest.put("PAGESTYLE", pageStyle);
    }
    
    /**
     * URL for the image you want to appear at the top left of the payment 
     * page. The image has a maximum size of 750 pixels wide by 90 pixels high. 
     * PayPal recommends that you provide an image that is stored on a secure 
     * (https) server. If you do not specify an image, the business name is 
     * displayed.
     * Character length and limit: 127 single-byte alphanumeric characters
     * 
     * @param imgUrl
     * @throws IllegalArgumentException
     */
    public void setImage(String imgUrl) throws IllegalArgumentException {

        if (imgUrl.length() > 127) {
            throw new IllegalArgumentException("Character length exceeded 30 " 
                    + "characters");
        }
        nvpRequest.put("HDRIMG", imgUrl);
    }

    /**
     * Sets the border color around the header of the payment page. The border 
     * is a 2-pixel perimeter around the header space, which is 750 pixels wide 
     * by 90 pixels high. By default, the color is black.
     * Character length and limitation: Six character HTML hexadecimal color 
     * code in ASCII
     * 
     * @param hexColor
     * @throws IllegalArgumentException
     */
    public void setBorderColor(String hexColor) 
            throws IllegalArgumentException {

        /* allowed characters 0-9 and a-f. Exactly 6 characters */
        Pattern pattern = Pattern.compile("^[0-9,a-f,A-F]{6}$");
        Matcher matcher = pattern.matcher(hexColor);
        if (!matcher.find()) {
            throw new IllegalArgumentException("Hex color" + hexColor + 
                    " is not valid.");
        }
        nvpRequest.put("HDRBORDERCOLOR", hexColor);
    }
	
    /**
     * Sets the background color for the header of the payment page. By 
     * default, the color is white.
     * Character length and limitation: Six character HTML hexadecimal color 
     * code in ASCII
     * 
     * @param hexColor
     * @throws IllegalArgumentException
     */
    public void setBackgroundColor(String hexColor) 
            throws IllegalArgumentException {

        /* allowed characters 0-9 and a-f. Exactly 6 characters */
        Pattern pattern = Pattern.compile("^[0-9,a-f,A-F]{6}$");
        Matcher matcher = pattern.matcher(hexColor);
        if (!matcher.find()) {
            throw new IllegalArgumentException("Hex color" + hexColor + 
                    " is not valid.");
        }
        nvpRequest.put("HDRBACKCOLOR", hexColor);
    }

    /**
     * Sets the background color for the payment page. By default, the color is
     * white.
     * Character length and limitation: Six character HTML hexadecimal color
     * code in ASCII
     *
     * @param hexColor
     * @throws IllegalArgumentException
     */
    public void setPayFlowColor(String hexColor)
            throws IllegalArgumentException {

        /* allowed characters 0-9 and a-f. Exactly 6 characters */
        Pattern pattern = Pattern.compile("^[0-9,a-f,A-F]{6}$");
        Matcher matcher = pattern.matcher(hexColor);
        if (!matcher.find()) {
            throw new IllegalArgumentException("Hex color" + hexColor +
                    " is not valid.");
        }
        nvpRequest.put("PAYFLOWCOLOR", hexColor);
    }
	
    /**
     * How you want to obtain payment:
     * <ul>
     *  <li>
     *      Sale indicates that this is a final sale for which you are 
     *      requesting payment. (Default)
     *  </li>
     *  <li>
     *      Authorization indicates that this payment is a basic authorization 
     *      subject to settlement with PayPal Authorization & Capture.
     *  </li>
     *  <li>
     *      Order indicates that this payment is an order authorization subject 
     *      to settlement with PayPal Authorization & Capture.
     *  </li>
     * </ul>
     * If the transaction does not include a one-time purchase, this field is 
     * ignored.
     * Note:
     * You cannot set this value to Sale in SetExpressCheckout request and then 
     * change this value to Authorization or Order on the final API 
     * DoExpressCheckoutPayment request. If the value is set to Authorization 
     * or Order in SetExpressCheckout, the value may be set to Sale or the same 
     * value (either Authorization or Order) in DoExpressCheckoutPayment.
     * 
     * @param paymentAction
     */
    public void setPaymentAction(PaymentAction paymentAction) {
        nvpRequest.put("PAYMENTACTION", paymentAction.getValue());
    }

    /**
     * Email address of the buyer as entered during checkout. PayPal uses this 
     * value to pre-fill the PayPal membership sign-up portion of the PayPal 
     * login page.
     * Character length and limit: 127 single-byte alphanumeric characters
     * 
     * @param email
     * @throws IllegalArgumentException
     */
    public void setEmail(String email) throws IllegalArgumentException {

        if (!Validator.isValidEmail(email)) {
            throw new IllegalArgumentException("Email is not valid");
        }

        if (email.length() > 127) {
            throw new IllegalArgumentException("Email can be maximum 127 " 
                    + "characters long.");
        }
        nvpRequest.put("EMAIL", email);
    }

    /**
     * Type of checkout flow:
     * <ul>
     *  <li><b>Sole:</b> Express Checkout for auctions</li>
     *  <li><b>Mark:</b> Normal Express Checkout</li>
     * </ul>
     *
     * @param solutionType
     */
    public void setSolutionType(SolutionType solutionType) {
        nvpRequest.put("SOLUTIONTYPE", solutionType.toString());
    }

    /**
     * Type of PayPal page to display:
     * <ul>
     *  <li><b>Billing:</b> non-PayPal account</li>
     *  <li><b>Login:</b> PayPal account login</li>
     * </ul>
     * 
     * @param landingPage
     */
    public void setLandingPage(LandingPage landingPage) {
        nvpRequest.put("LANDINGPAGE", landingPage.toString());
    }

    /**
     * Type of channel:
     * <ul>
     *  <li><b>Merchant:</b> non-auction seller</li>
     *  <li><b>eBayItem:</b> eBay auction</li>
     * </ul>
     * 
     * @param channelType
     */
    public void setChannelType(ChannelType channelType) {
        nvpRequest.put("CHANNELTYPE", channelType.toString());
    }
	
    /**
     * The URL on the merchant site to redirect to after a successful giropay 
     * payment.
     * Use this field only if you are using giropay or bank transfer payment 
     * methods in Germany.
     * 
     * @param url
     */
    public void setGiroPaySuccessUrl(String url) {
        nvpRequest.put("GIROPAYSUCCESSURL", url);
    }

    /**
     * The URL on the merchant site to redirect to after a unsuccessful giropay 
     * payment.
     * Use this field only if you are using giropay or bank transfer payment 
     * methods in Germany.
     * 
     * @param url
     */
    public void setGiroCancelUrl(String url) {
        nvpRequest.put("GIROPAYCANCELURL", url);
    }
	
    /**
     * The URL on the merchant site to transfer to after a bank transfer 
     * payment.
     * Use this field only if you are using giropay or bank transfer payment 
     * methods in Germany.
     * 
     * @param url
     */
    public void setBankTxPendingUrl(String url) {
        nvpRequest.put("BANKTXNPENDINGURL", url);
    }

	/**
	 * Sets address fields
	 * 
	 * @param address
	 */
	public void setAddress(Address address) {
		nvpRequest.putAll(address.getNVPRequest());
	}

	/**
	 * Sets shipping options
	 * 
     * @param options
	 */
	public void setShippingOptions(ShippingOptions[] options) {

        /* check items */
        if (options == null || options.length == 0) {
            throw new IllegalArgumentException("You did not supply options.");
        }

        /* iterate supplied array */
        int x = 0;  // this is only for exception message
        for (ShippingOptions option : options) {
            /* item cannot be null */
            if (option == null) {
                throw new IllegalArgumentException("Option at index " + x
                        + " is not set.");
            }
            this.shippingOptions.add(new HashMap<String, String>(option.getNVPRequest()));
            x++;
        }
	}

	/**
	 * Sets billing agreement (recurring payments etc.). Maximum allowed
     * billing agreements is 10
	 * 
	 * @param agreements
	 */
	public void setBillingAgreement(BillingAgreement[] agreements) {

        /* check items */
        if (agreements == null || agreements.length == 0) {
            throw new IllegalArgumentException("You did not supply any agreement.");
        }
        if (agreements.length > 10) {
            throw new IllegalArgumentException("Maximum allowed agreements is 10");
        }

        /* iterate supplied array */
        int x = 0;  // this is only for exception message
        for (BillingAgreement agreement : agreements) {
            /* item cannot be null */
            if (agreement == null) {
                throw new IllegalArgumentException("Agreement at index " + x
                        + " is not set.");
            }
            this.billingAgreement.add(new HashMap<String, String>(agreement.getNVPRequest()));
            x++;
        }
	}

	/**
	 * Sets buyer details
	 * 
	 * @param buyer
	 */
	public void setBuyerDetails(BuyerDetails buyer) {
		nvpRequest.putAll(new HashMap<String, String>(buyer.getNVPRequest()));
	}

    /**
     *
     * @param address shipping address
     */
    public void setShippingAddress(ShipToAddress address) {
        nvpRequest.putAll(new HashMap<String, String>(address.getNVPRequest()));
    }

    public Map<String, String> getNVPRequest() {

		/* hash map holding response */
		HashMap<String, String> nvp = new HashMap<String, String>(nvpRequest);

        /* shipping options */
		for (int i = 0; i < shippingOptions.size(); i++) {
			for (Map.Entry<String, String> entry
					: shippingOptions.get(i).entrySet()) {

				/* KEYn VALUE */
				nvp.put(entry.getKey() + i, entry.getValue());
			}
		}

        /* billing agreement */
		for (int i = 0; i < billingAgreement.size(); i++) {
			for (Map.Entry<String, String> entry
					: billingAgreement.get(i).entrySet()) {

				/* KEYn VALUE */
				nvp.put(entry.getKey() + i, entry.getValue());
			}
		}

        return nvp;
    }

    public void setNVPResponse(Map<String, String> nvpResponse) {
        this.nvpResponse = new HashMap<String, String>(nvpResponse);
    }

    public Map<String, String> getNVPResponse() {
        return new HashMap<String, String>(nvpResponse);
    }

    @Override
    public String toString() {

        StringBuffer str = new StringBuffer("instance of SetExpressCheckout ");
        str.append("class with the vlues: nvpRequest - ");
        str.append(nvpRequest.toString());
		str.append("; nvpResponse - ");
		str.append(nvpResponse.toString());

		return str.toString();
    }
}
